﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ConceptsFramework.AbstractConceptElements;
using ConceptsFramework.Containers.Generic;
using ConceptsFramework.DerivedRelationships;
using NLog;
using PatternExplorer.DerivedConcepts;

namespace PatternExplorer.DerivedContainers
{
    public class TypeSummarySingletonContainer : BindableConceptContainer<TypeSummary>
    {
        private readonly NLog.Logger logger = LogManager.GetCurrentClassLogger();
        public Dictionary<string, string> InheritanceTrees = new Dictionary<string, string>();
        private static TypeSummarySingletonContainer _Instance;
        public static TypeSummarySingletonContainer Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new TypeSummarySingletonContainer();
                return _Instance;
            }
        }

        private TypeSummarySingletonContainer()
        {
            base.ConceptKeyRetriever = (a => a.Name);
        }

        public void AddInheritance(string strTypeDesc, string strBaseTypeDesc)
        {
            if (strTypeDesc != null)
            {
                if (!InheritanceTrees.ContainsKey(strTypeDesc))
                {
                    InheritanceTrees.Add(strTypeDesc, strBaseTypeDesc);
                }
            }
        }

        public void AddInheritanceStack(List<Type> InheritanceStack)
        {
            if (InheritanceStack.Count > 1)
            {
                Queue<Type> allTypes = new Queue<Type>(InheritanceStack);
                Type initialType = allTypes.Dequeue();
                while (allTypes.Count > 0)
                {
                    var BaseType = allTypes.Dequeue();
                    if (BaseType != typeof(System.Object))
                        AddInheritance(initialType.FullName, BaseType.FullName);
                    initialType = BaseType;
                }
            }
        }

        /// <summary>
        /// Retrieve a method from the current repository
        /// </summary>
        /// <param name="strType"></param>
        /// <param name="strMethod"></param>
        /// <returns></returns>
        private MethodSummary getMethod(string strType, string strMethodName)
        {
            if (this.Container.ContainsKey(strType))
            {
                TypeSummary foundHostTypeConcept = this.Container[strType] as TypeSummary;
                if (foundHostTypeConcept != null)
                {
                    MethodSummary returnedMethod = null;
                    if (foundHostTypeConcept.PublicMethods.Container.ContainsKey(strMethodName))
                        returnedMethod = foundHostTypeConcept.PublicMethods.Container[strMethodName];
                    else if (foundHostTypeConcept.PrivateMethods.Container.ContainsKey(strMethodName))
                        returnedMethod = foundHostTypeConcept.PrivateMethods.Container[strMethodName];
                    return returnedMethod;
                }
            }
            else
            {
                // Invalid method requested.
                var invalidMethod = strType + "." + strMethodName;
            }
            return null;
        }

        // Possible IL code templates are listed below.
        // ===================================================================
        // newobj ReturnType ObjectType .ctor()
        // call ObjectType MethodName
        // callvirt ObjectType (get_ or set_) + MethodName  
        // callvirt ObjectType MethodName (interface call)
        // ===================================================================

        private AbstractBindableConcept getCallTargetFromIL(string strILrow, out bool isNewObjectOrSetter)
        {
            List<string> opCodesIL = new List<string>(strILrow.Split(' '));
            isNewObjectOrSetter = false;
            if (opCodesIL.Count<3)
            {
                logger.Debug("Unknow interpreted IL code found " + strILrow);
            }
            else
            {
                string strType = "";
                string strMethodNameWithAccessor = "";
                switch(opCodesIL[0])
                {
                    case "call":
                        strType = opCodesIL[2];
                        strMethodNameWithAccessor = opCodesIL[3].Replace("(", "").Replace(")", "");
                        if (strMethodNameWithAccessor.StartsWith("set_"))
                        {
                            isNewObjectOrSetter = true;
                        }
                        return getMethod(strType, strMethodNameWithAccessor);
                        break;
                    case "newobj":
                        strType = opCodesIL[2];
                        int genericIndex = strType.IndexOf("+<>", System.StringComparison.Ordinal);
                        if (genericIndex != -1)
                        {
                            strType = strType.Substring(0, genericIndex);
                        }
                        isNewObjectOrSetter = true;
                        if (strType != "")
                        {
                            if (strType.Contains(('`')))
                            {
                                int genericArgumentDelimiter = strType.IndexOf('`');
                                string strBaseType = strType.Substring(0, genericArgumentDelimiter);
                                if (this.Container.ContainsKey(strBaseType))
                                {
                                    AbstractBindableConcept foundNewObjectConcept = this.Container[strBaseType];

                                    int leftBracket = genericArgumentDelimiter = strType.IndexOf('[');
                                    int rightBracket = genericArgumentDelimiter = strType.IndexOf(']');

                                    string strGenericArguments = strType.Substring((leftBracket+1),
                                                                                   (rightBracket - leftBracket-1));
                                    List<string> genericArguments =
                                        new List<string>(strGenericArguments.Split(new char[] {','}));

                                    if (genericArguments.Count == 1)
                                    {
                                        if (!(foundNewObjectConcept is SingleGenericTypeSummary))
                                        {
                                            logger.Debug("The type " + strType + " analyzed in " + strILrow + " has not been registered as a single-argument generic.");
                                        }
                                        if (genericArguments[0] == ((SingleGenericTypeSummary)foundNewObjectConcept).BaseGenericArgumentType)
                                        {
                                            return foundNewObjectConcept;
                                        }
                                        else
                                        {
                                            return
                                                ((SingleGenericTypeSummary) foundNewObjectConcept).
                                                    GetGenericTypeVariation(genericArguments[0]);
                                        }
                                    }
                                    else if (genericArguments.Count > 1)
                                    {
                                        if (!(foundNewObjectConcept is MultipleGenericTypeSummary))
                                        {
                                            logger.Debug("The type " + strType + " analyzed in " + strILrow + " has not been registered as a single-argument generic.");
                                        }

                                        MultipleGenericTypeSummary aSummary = new MultipleGenericTypeSummary();
                                        aSummary.BaseGenericArgumentTypes = genericArguments;

                                        if (((MultipleGenericTypeSummary)foundNewObjectConcept).BaseGenericArgumentTypes == genericArguments)
                                            return foundNewObjectConcept;
                                        else
                                        return
                                            ((MultipleGenericTypeSummary) foundNewObjectConcept).GetGenericTypeVariation
                                                (genericArguments);
                                    }
                                }
                                else
                                {
                                    logger.Debug("The type " + strType + " analyzed in " + strILrow + " has not been registered as a single-argument generic.");
                                }
                            }
                            else
                            {
                                AbstractBindableConcept foundNewObjectConcept = this.Container[strType];
                                return foundNewObjectConcept;
                            }
                        }
                        break;
                    case "callvirt":
                        strType = opCodesIL[2];
                        strMethodNameWithAccessor = opCodesIL[3].Replace("(", "").Replace(")", "");
                        if (strMethodNameWithAccessor.StartsWith("set_"))
                        {
                            isNewObjectOrSetter = true;
                        }
                        return getMethod(strType, strMethodNameWithAccessor);
                        break;
                    default:
                        throw new ApplicationException("Unknow interpreted IL code found " + strILrow);
                }
                
            }
            return null;
        }

        
        /// <summary>
        /// Commit the call relationships found between concepts to the global 
        /// relationships repository
        /// </summary>
        /// <param name="aMethodSummary"></param>
        private void CommitCallRelationships(MethodSummary aMethodSummary)
        {
            if (aMethodSummary.MethodsExternalCallsILCode.Count>0)
            {
                foreach (string strILCode in aMethodSummary.MethodsExternalCallsILCode)
                {
                    bool isNewObjectOrSetter = false;
                    AbstractBindableConcept foundConcept = getCallTargetFromIL(strILCode,
                                                                               out isNewObjectOrSetter);
                    if (foundConcept != null)
                    {
                        if ((isNewObjectOrSetter) && (foundConcept is TypeSummary))
                        {
                            CallsNewObjectRelationship aRelationship = new CallsNewObjectRelationship();
                            aRelationship.LeftConcept = aMethodSummary;
                            aRelationship.RightConcepts.Add(foundConcept);
                            aRelationship.Commit<CallsNewObjectRelationship, IsConstructorCalledByRelationship>(false);

                        }
                        else
                        {
                            CallsRelationship aCallRelationship = new CallsRelationship();
                            aCallRelationship.LeftConcept = aMethodSummary;
                            aCallRelationship.RightConcepts.Add(foundConcept);
                            aCallRelationship.Commit<CallsRelationship, IsCalledByRelationship>(false);
                        }
                    }
                }
            }
        }

        public void BuildCallTreeRelationships()
        {
            foreach (var aConcept in this)
            {
                foreach(var aPrivateMethod in aConcept.PrivateMethods)
                {
                    CommitCallRelationships(aPrivateMethod);
                }

                foreach (var aPublicMethod in aConcept.PublicMethods)
                {
                    CommitCallRelationships(aPublicMethod);
                }
            }
        }

        public void BuildInheritenceRelationships()
        {
            foreach(string strKeyName in InheritanceTrees.Keys)
            {
                string strParentKeyName = InheritanceTrees[strKeyName];
                if (strParentKeyName != null)
                {
                    TypeSummary childTypeSummary = this.Retrieve(strKeyName);
                    TypeSummary parentTypeSummary = this.Retrieve(strParentKeyName);
                    if ((parentTypeSummary != null) && (childTypeSummary != null))
                    {
                        IsTypeOfRelationship anIsTypeOfRelationship = new IsTypeOfRelationship();
                        anIsTypeOfRelationship.LeftConcept = childTypeSummary;
                        anIsTypeOfRelationship.RightConcepts.Add(parentTypeSummary);
                        anIsTypeOfRelationship.Commit();
                    }
                }
            }
        }
    }
}
